package animeutl

import (
	"fmt"
	"gitee.com/xiakai001/goutl/errutl"
	"gitee.com/xiakai001/goutl/goutl"
	"github.com/bytedance/sonic"
	ffmpeg "github.com/u2takey/ffmpeg-go"
	"regexp"
	"strconv"
	"strings"
	"time"
)

const (
	resolutionMin = 1920 * 1080
)

type MVType string

const (
	MVTypeED MVType = "ED"
	MVTypeOP MVType = "OP"
	MVTypeMV MVType = "MV"
	MVTypePV MVType = "PV"
	MVTypeCM MVType = "CM"
	MVTypeSP MVType = "SP"
)

type VideoCodeType string

const (
	VideoCodeTypeHEVC VideoCodeType = "HEVC"
	VideoCodeTypeAVC  VideoCodeType = "AVC"
	VideoCodeTypeVP9  VideoCodeType = "VP9"
	VideoCodeTypeAV1  VideoCodeType = "AV1"
)

type VideoFpsType string

const (
	VideoFpsTypeNormal VideoFpsType = ""
	VideoFpsType60     VideoFpsType = "60FPS"
)

type VideoResolutionType string

const (
	VideoResolutionTypeNormal VideoResolutionType = ""
	VideoResolutionType2K     VideoResolutionType = "2K"
	VideoResolutionType4K     VideoResolutionType = "4K"
)

type AudioCodeType string

const (
	AudioCodeTypeFLAC   AudioCodeType = "FLAC"
	AudioCodeTypeAAC    AudioCodeType = "AAC"
	AudioCodeTypeOPUS   AudioCodeType = "OPUS"
	AudioCodeTypeTrueHD AudioCodeType = "TrueHD"
	AudioCodeTypeDTS    AudioCodeType = "DTS"
	AudioCodeTypeAC3    AudioCodeType = "AC3"
	AudioCodeTypeALAC   AudioCodeType = "ALAC"
)

var (
	strToMVTypeMap = map[string]MVType{
		"ED": MVTypeED,
		"OP": MVTypeOP,
		"MV": MVTypeMV,
		"PV": MVTypePV,
		"CM": MVTypeCM,
		"SP": MVTypeSP,
	}
)

var (
	probeFonts        = []string{"ttf", "otf"}
	probeFontMap      map[string]bool // 上面的字体
	probeImages       = []string{"png", "jpg", "jpeg", "mjpeg", "tif", "bmp", "tif", "webp"}
	probeImageMap     map[string]bool // 上面的图片格式
	probeVideoCodeMap = map[string]VideoCodeType{
		"h264": VideoCodeTypeAVC,
		"hevc": VideoCodeTypeHEVC,
		"vp9":  VideoCodeTypeVP9,
		"av1":  VideoCodeTypeAV1,
	}
	probeAudioCodeMap = map[string]AudioCodeType{
		"flac":   AudioCodeTypeFLAC,
		"aac":    AudioCodeTypeAAC,
		"opus":   AudioCodeTypeOPUS,
		"truehd": AudioCodeTypeTrueHD,
		"dts":    AudioCodeTypeDTS,
		"ac3":    AudioCodeTypeAC3,
		"alac":   AudioCodeTypeALAC,
	}
)

func init() {
	probeFontMap = make(map[string]bool, len(probeFonts))
	for _, font := range probeFonts {
		probeFontMap[font] = true
	}
	probeImageMap = make(map[string]bool, len(probeImages))
	for _, image := range probeImages {
		probeImageMap[image] = true
	}
}

type MkvMVNameInfo struct {
	FullName        string
	AnimeName       string
	MVType          MVType
	Number          *int64
	NumberLength    int
	SubNumber       *int64
	SubNumberLength int
	//Season    *int
	//Episode   *int
	SongName     string
	Source       string
	VideoCodeStr string
	AudioCodeStr string
}

func (mv *MkvMVNameInfo) GetNumber() int64 {
	if mv.Number == nil {
		return 0
	}
	return *mv.Number
}

func (mv *MkvMVNameInfo) GetSubNumber() int64 {
	if mv.SubNumber == nil {
		return 0
	}
	return *mv.SubNumber
}

var mkvMVNameRegexp = regexp.MustCompile("^(.+?) (ED|OP|MV|PV|CM|SP)(\\d*)(v(\\d+))?(\\([SEMOPGF.0-9]+\\))? (.+) \\[(.+)]( \\((.+)\\))?【((.+) (.+))】\\.mkv$")

// GetMkvMVNameInfo 根据名字获取信息
func GetMkvMVNameInfo(name string) (*MkvMVNameInfo, error) {

	if strings.Contains(name, "  ") {
		return nil, errutl.New("连续空格")
	}

	group := mkvMVNameRegexp.FindStringSubmatch(name)
	if len(group) < 14 {
		return nil, errutl.New("正则匹配失败")
	}

	// 动漫名
	animeName := group[1]
	if len(animeName) == 0 {
		return nil, errutl.New("动漫名不存在")
	}

	// mv类型
	mvType, ok := strToMVTypeMap[group[2]]
	if !ok {
		return nil, errutl.New("mv类型(%s)错误", group[2])
	}
	var numberPtr *int64
	var numberLen int
	if len(group[3]) != 0 {
		// mv编号
		number, err := strconv.ParseInt(group[3], 10, 64)
		if err != nil {
			return nil, errutl.Wrap(err)
		}
		if number <= 0 {
			return nil, errutl.New("number(%s)错误", group[3])
		}
		numberPtr = &number
		numberLen = len(group[3])
	}
	var subNumberPtr *int64
	var subNumberLen int
	if len(group[5]) != 0 {
		// mv子编号
		subNumber, err := strconv.ParseInt(group[5], 10, 64)
		if err != nil {
			return nil, errutl.Wrap(err)
		}
		if subNumber <= 0 {
			return nil, errutl.New("subNumber(%s)错误", group[5])
		}
		subNumberPtr = &subNumber
		subNumberLen = len(group[5])
	}

	// 歌曲名
	songName := group[7]
	if len(songName) == 0 {
		return nil, errutl.New("歌曲名不存在")
	}
	source := group[8]
	if len(source) == 0 {
		return nil, errutl.New("来源不存在")
	}

	// 特殊tag需要按照顺序排序
	tags := strings.Split(group[10], " ")
	goutl.SortAsc(tags)
	if strings.Join(tags, " ") != group[10] {
		return nil, errutl.New("tag顺序不对，tags=%s", group[10])
	}

	videoCodeStr := group[12]
	if len(videoCodeStr) == 0 {
		return nil, errutl.New("video code不存在")
	}
	audioCodeStr := group[13]
	if len(audioCodeStr) == 0 {
		return nil, errutl.New("audio code不存在")
	}

	return &MkvMVNameInfo{
		FullName:        name,
		AnimeName:       animeName,
		MVType:          mvType,
		Number:          numberPtr,
		NumberLength:    numberLen,
		SubNumber:       subNumberPtr,
		SubNumberLength: subNumberLen,
		SongName:        songName,
		Source:          source,
		VideoCodeStr:    videoCodeStr,
		AudioCodeStr:    audioCodeStr,
	}, nil
}

// GetProbe 获取视频编码信息
func GetProbe(fileName string) (*ProbeResult, error) {
	absolutePath := goutl.GetAbsolutePath(fileName)
	// 获取视频编码信息
	probeStr, err := ffmpeg.Probe(absolutePath,
		map[string]interface{}{
			"v":             "quiet",
			"print_format":  "json",
			"show_streams":  "",
			"show_chapters": "",
		})
	if err != nil {
		return nil, errutl.Wrap(err)
	}

	probeResult := &ProbeResult{}
	err = sonic.Unmarshal([]byte(probeStr), probeResult)
	if err != nil {
		return nil, errutl.Wrap(err)
	}

	return probeResult, nil
}

func GetProbeCode(fileName string) (string, string, error) {

	probeResult, err := GetProbe(fileName)
	if err != nil {
		return "", "", errutl.Wrap(err)
	}

	if len(probeResult.Chapters) != 0 {
		return "", "", errutl.New("存在章节")
	}

	videoCodeStr := ""                             // 视频编码格式
	audioTypeMap := make(map[AudioCodeType]int, 3) // 多个音频类型
	hasFont := false                               // 是否存在字体
	hasAss := false                                // 是否存在字幕
	for _, stream := range probeResult.Streams {
		switch stream.CodecType {
		case "video":
			if probeImageMap[stream.CodecName] {
				return "", "", errutl.New("存在图片")
			}
			if len(videoCodeStr) != 0 {
				return "", "", errutl.New("存在多个视频轨")
			}
			videoType, ok := probeVideoCodeMap[stream.CodecName]
			if !ok {
				return "", "", errutl.New("未知视频类型, CodecName=%s", stream.CodecName)
			}
			videoCodeStr = string(videoType)

			index := strings.LastIndex(stream.AvgFrameRate, "/")
			var avgFrameRate float64
			if index <= 0 {
				avgFrameRate, _ = strconv.ParseFloat(stream.AvgFrameRate, 64)
			} else {
				aa, _ := strconv.ParseFloat(stream.AvgFrameRate[0:index], 64)
				bb, _ := strconv.ParseFloat(stream.AvgFrameRate[index+1:], 64)
				avgFrameRate = aa / bb
			}
			if avgFrameRate <= 0 {
				return "", "", errutl.New("帧率获取失败, AvgFrameRate=%s", stream.AvgFrameRate)
			}
			if avgFrameRate >= 59 {
				// 60帧视频
				videoCodeStr = string(VideoFpsType60) + "_" + videoCodeStr
			}

			if stream.Height*stream.Width < resolutionMin {
				return "", "", errutl.New("低分辨率=%dx%d", stream.Width, stream.Height)
			}

			if (stream.Height >= 3800 && stream.Width >= 2160) || (stream.Height >= 2160 && stream.Width >= 3800) {
				// 4k视频
				videoCodeStr = string(VideoResolutionType4K) + "_" + videoCodeStr
			} else if (stream.Width >= 2560 && stream.Height >= 1440) || (stream.Width >= 1920 && stream.Height >= 1920) {
				// 2k视频
				videoCodeStr = string(VideoResolutionType2K) + "_" + videoCodeStr
			}

		case "audio":
			audioType, ok := probeAudioCodeMap[stream.CodecName]
			if !ok {
				return "", "", errutl.New("未知音频类型, CodecName=%s", stream.CodecName)
			}
			audioTypeMap[audioType]++
		case "subtitle":
			hasAss = true
		case "attachment":
			if probeFontMap[stream.CodecName] {
				hasFont = true
				continue
			}
			if len(stream.Tags.Mimetype) != 0 &&
				stream.Tags.Mimetype[0:strings.IndexByte(stream.Tags.Mimetype, '/')] == "font" {
				hasFont = true
				continue
			}
			return "", "", errutl.New("未知attachment类型, CodecName=%s", stream.CodecName)
		default:
			return "", "", errutl.New("未知CodecType类型, CodecType=%s", stream.CodecType)
		}
	}

	if len(videoCodeStr) == 0 {
		return "", "", errutl.New("无视频轨")
	}
	if len(audioTypeMap) == 0 {
		return "", "", errutl.New("无音频轨")
	}
	if !hasAss && hasFont {
		return "", "", errutl.New("不应该有字体")
	}

	var audioCodes []string
	// 把音频按数量组合，如FLACx7
	for code, n := range audioTypeMap {
		if n == 1 {
			audioCodes = append(audioCodes, string(code))
		} else {
			audioCodes = append(audioCodes, string(code)+"x"+strconv.Itoa(n))
		}
	}

	// 排序
	goutl.SortAsc(audioCodes)

	audioCodeStr := strings.Join(audioCodes, "_")

	return videoCodeStr, audioCodeStr, nil
}

// CheckMkvMVCode 检查编码对不对
func CheckMkvMVCode(fileName string) error {
	nameInfo, err := GetMkvMVNameInfo(fileName)
	if err != nil {
		return errutl.Wrap(err)
	}

	videoCodeStr, audioCodeStr, err := GetProbeCode(fileName)
	if err != nil {
		return errutl.Wrap(err)
	}

	if nameInfo.VideoCodeStr != videoCodeStr {
		return errutl.New("%s != %s\n", nameInfo.VideoCodeStr, videoCodeStr)
	}
	if nameInfo.AudioCodeStr != audioCodeStr {
		return errutl.New("%s != %s\n", nameInfo.AudioCodeStr, audioCodeStr)
	}

	return nil
}

var lenToNumTopMap = map[int]int64{
	1: 0,
	2: 10,
	3: 100,
	4: 1000,
}

var lenToNumButtonMap = map[int]int64{
	1: 9,
	2: 99,
	3: 999,
	4: 9999,
}

func CheckMkvMVList(mvNameList []string) ([]string, error) {

	errMVNameList := make([]string, 0, 10)

	// 动漫名 -> op/ed -> 名字信息list
	mvNameInfoMap := make(map[string]map[MVType][]*MkvMVNameInfo, 1000)
	for _, name := range mvNameList {

		// 获取名字信息，用于后续检查op ed标号等信息对不对
		info, err := GetMkvMVNameInfo(name)
		if err != nil {
			errMVNameList = append(errMVNameList, fmt.Sprintf("name=%s err=%s", name, errutl.Cause(err).Error()))
			continue
		}
		m := mvNameInfoMap[info.AnimeName]
		if m == nil {
			m = make(map[MVType][]*MkvMVNameInfo, 25)
			mvNameInfoMap[info.AnimeName] = m
		}
		m[info.MVType] = append(m[info.MVType], info)
	}

	// list check
	for animeName, typeInfoMap := range mvNameInfoMap {
		for mvType, infoList := range typeInfoMap {
			// op/ed -> 名字信息list

			// number数量
			numMap := make(map[int64]map[int64]int, len(infoList))
			for _, info := range infoList {
				m := numMap[info.GetNumber()]
				if m == nil {
					m = map[int64]int{}
					numMap[info.GetNumber()] = m
				}
				m[info.GetSubNumber()]++
			}

			if len(numMap) > 1 && len(numMap[0]) != 0 {
				// 应该是MV1、MV2；却是MV、MV2
				errMVNameList = append(errMVNameList, fmt.Sprintf("list name err name=%s %s",
					animeName, mvType))
			}

			for number, subNumMap := range numMap {
				if len(subNumMap) > 1 && subNumMap[0] > 0 {
					// 应该是MVv1、MVv2；却是MV、MVv2
					errMVNameList = append(errMVNameList, fmt.Sprintf("list name err name=%s %s%d",
						animeName, mvType, number))
				}
				for subNumber, count := range subNumMap {
					if count > 1 {
						// 应该是01、02、03；却是01、02、02
						errMVNameList = append(errMVNameList, fmt.Sprintf("list name err name=%s %s%dv%d",
							animeName, mvType, number, subNumber))
					}
				}
			}

		}

	}

	// 长度 check
	for animeName, typeInfoMap := range mvNameInfoMap {
		for mvType, infoList := range typeInfoMap {
			numLen := 0
			numTopOk := false
			subInfoMap := make(map[int64][]*MkvMVNameInfo, len(infoList))
			for _, info := range infoList {
				if numLen != 0 && numLen != info.NumberLength {
					// 应该是01、02、03；却是01、2、03
					errMVNameList = append(errMVNameList, fmt.Sprintf("list number len err name=%s %s%d",
						animeName, mvType, info.GetNumber()))
				}
				numLen = info.NumberLength
				if info.GetNumber() >= lenToNumTopMap[numLen] && info.GetNumber() <= lenToNumButtonMap[numLen] {
					numTopOk = true
				}
				subInfoMap[info.GetNumber()] = append(subInfoMap[info.GetNumber()], info)
			}
			if !numTopOk {
				// 应该是1、2、3；却是01、02、03
				errMVNameList = append(errMVNameList, fmt.Sprintf("list number top err name=%s %s",
					animeName, mvType))
			}
			for num, subInfoList := range subInfoMap {
				subNumLen := 0
				subNumTopOk := false
				for _, info := range subInfoList {
					if subNumLen != 0 && subNumLen != info.SubNumberLength {
						// 应该是01、02、03；却是01、2、03
						errMVNameList = append(errMVNameList, fmt.Sprintf("list name err name=%s %s%dv%d",
							animeName, mvType, num, info.GetSubNumber()))
					}
					subNumLen = info.SubNumberLength
					if info.GetSubNumber() >= lenToNumTopMap[subNumLen] && info.GetSubNumber() <= lenToNumButtonMap[subNumLen] {
						subNumTopOk = true
					}
				}
				if !subNumTopOk {
					// 应该是1、2、3；却是01、02、03
					errMVNameList = append(errMVNameList, fmt.Sprintf("list number top err name=%s %s%d",
						animeName, mvType, num))
				}
			}
		}
	}

	return errMVNameList, nil
}

type ProbeResult struct {
	Streams []struct {
		CodecName     string `json:"codec_name"`
		CodecLongName string `json:"codec_long_name"`
		CodecType     string `json:"codec_type"`
		Height        int    `json:"height"`
		Width         int    `json:"width"`
		AvgFrameRate  string `json:"avg_frame_rate"`
		Duration      string `json:"duration"`
		Tags          struct {
			Mimetype string `json:"mimetype"`
			DURATION string `json:"DURATION"`
		} `json:"tags"`
	} `json:"streams"`
	Chapters []struct { // 章节
		Id int64 `json:"id"`
	} `json:"chapters"`
	Format struct {
		Duration string `json:"duration"`
	} `json:"format"`
}

func (p *ProbeResult) GetVideoFrameRate() string {
	for _, stream := range p.Streams {
		if stream.CodecType == "video" {
			return stream.AvgFrameRate
		}
	}
	return ""
}

func (p *ProbeResult) GetVideoDuration() (float64, error) {
	var duration float64
	var err error
	for _, stream := range p.Streams {
		if strings.ToLower(stream.CodecType) == "video" {
			var d float64
			if len(stream.Duration) != 0 {
				d, err = strconv.ParseFloat(stream.Duration, 64)
				if err != nil {
					return 0, errutl.Wrap(err)
				}
			}
			if d <= 0 && len(stream.Tags.DURATION) != 0 {
				t, err := time.Parse("2006-01-02 15:04:05.000000000", "1970-01-01 "+stream.Tags.DURATION)
				if err != nil {
					return 0, errutl.Wrap(err)
				}
				d = float64(t.UnixNano()) / float64(time.Second)
			}

			if d > 0 {
				if duration > 0 {
					return 0, errutl.New("存在多个视频轨")
				} else {
					duration = d
				}
			}
		}
	}

	if duration > 0 {
		return duration, nil
	}

	duration, err = strconv.ParseFloat(p.Format.Duration, 64)
	if err != nil {
		return 0, errutl.Wrap(err)
	}

	return duration, nil

}
